package xiaojian.toolkit.redis;

import com.google.common.base.Preconditions;
import com.google.common.collect.Sets;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Transaction;

public class PoolInvocationHandler implements InvocationHandler {

	public static Set<String> readMethod = Sets.newHashSet("keys", "randomKey",
			"mget", "rpoplpush", "sinter", "sunion", "sdiff", "blpop", "get",
			"hget", "zrangeByScore", "zrevrangeByScore", "zrevrangeByScore",
			"strlen", "exists", "type", "ttl", "hmget", "hexists", "hgetAll",
			"llen", "lrange", "lindex", "smembers", "sismember",
			"srandmember");

	private static final Logger logger = LoggerFactory
			.getLogger(PoolInvocationHandler.class);
	private Jedis2Pool writeJedisPool;
	private Jedis2Pool readJedisPool;

	public PoolInvocationHandler(Jedis2Pool writeJedisPool,
			Jedis2Pool readJedisPool) {
		Preconditions.checkArgument(writeJedisPool != null);
		this.writeJedisPool = writeJedisPool;
		if (readJedisPool == null) {
			this.readJedisPool = writeJedisPool;
		} else {
			this.readJedisPool = readJedisPool;
		}
	}

	public Object invoke(Object proxy, Method method, Object args[])
			throws Throwable {
		String mehtodName = method.getName();

		Jedis2 jedis;
		Jedis2Pool jedi2Pool = null;
		String poolName = null;
		if (readMethod.contains(mehtodName)) {
			jedis = (Jedis2) readJedisPool.getResource();
			jedi2Pool = readJedisPool;
			poolName = "readJedisPool";
		} else {
			jedis = (Jedis2) writeJedisPool.getResource();
			jedi2Pool = writeJedisPool;
			poolName = "writeJedisPool";
		}

		if (jedis == null) {
			throw new IllegalStateException("Cannot get redis connection.");
		}

		if (method.getName().equals("sync")) {
			try {
				sync((PipelineExecutor) args[0], jedis);
			} catch (Exception exception) {
				throw exception;
			} finally {
				jedi2Pool.returnResource(jedis);
			}
			return null;
		}

		if (method.getName().equals("syncAndReturnAll")) {
			try {
				return syncAndReturnAll((PipelineExecutor) args[0], jedis);
			} catch (Exception exception) {
				throw exception;
			} finally {
				jedi2Pool.returnResource(jedis);
			}
		}

		if (method.getName().equals("exec")) {
			try {
				return exec((TransactionExector) args[0], jedis);
			} catch (Exception exception) {
				throw exception;
			} finally {
				jedi2Pool.returnResource(jedis);
			}
		}

		Object value = null;

		try {
			value = method.invoke(jedis, args);
		} catch (Exception exception) {
			throw exception;
		} finally {
			jedi2Pool.returnResource(jedis);
		}

		logger.debug("Execute jedis command[" + method.getName() + "], args["
				+ ArrayUtils.toString(args) + "], result["
				+ String.valueOf(value) + "]. redisPool[" + poolName + "]");

		return value;
	}

	private List<Object> exec(TransactionExector executor, Jedis2 jedis) {
		Transaction t = jedis.multi();
		executor.transacted(t);
		return t.exec();
	}

	private void sync(PipelineExecutor executor, Jedis2 jedis) {
		Pipeline p = jedis.pipelined();
		executor.pipelined(p);
		p.sync();
	}

	private List<Object> syncAndReturnAll(PipelineExecutor executor,
			Jedis2 jedis) {
		Pipeline p = jedis.pipelined();
		executor.pipelined(p);
		return p.syncAndReturnAll();
	}
}
